/*
 License
 
 Copyright © 2012 Kevin Kimmich
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights to 
 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 the Software, and to permit persons to whom the Software is furnished to do so, 
 subject to the following conditions:
 
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
 FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include <signal.h>
#include "crcore.h"
#include "list.h"
#include "core_thread.h"
#include "stream.h"
#include "kuato_types.h"
#include "kuato_http_operations.h"
#include "method.h"
#include "kuato_server.h"
#include "messaging.h"


static void* listener_loop(int argc, void** arg);

connection_context* create_client_context(struct sockaddr_in addr)
{
	connection_context* ctx = malloc(sizeof(connection_context));
	if(ctx == NULL)
		return NULL;
	
	memset(ctx,0,sizeof(connection_context));
	
	ctx->type = CLIENT;
	
	ctx->fd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

	if(ctx->fd < 0)
		goto fail;
	
	struct linger lng = {0,0};
	if(setsockopt(ctx->fd,SOL_SOCKET,SO_LINGER,(void*)&lng,sizeof(struct linger)))
		goto fail;
	
	ctx->addr = addr;
	if(connect(ctx->fd,(struct sockaddr*)&ctx->addr,sizeof(struct sockaddr_in)))
	{
		if(errno != EINPROGRESS)
			goto fail;
	}		
	
	size_t optlen = sizeof(size_t);
	if(getsockopt(ctx->fd,SOL_SOCKET,SO_RCVBUF,&ctx->read_buffer_size,(socklen_t*)&optlen))
		goto fail;

	stream_init(&ctx->read_stream,UNBOUNDED);
	stream_init(&ctx->write_stream,UNBOUNDED);
	
	return ctx;
	
fail:
	destroy_context(ctx);
	return NULL;
}

//create a context that listens on address with a max_conn backlog
connection_context* create_listener_context(struct sockaddr_in addr, int max_conn,http_server_object* h)
{
	connection_context* ctx = malloc(sizeof(connection_context));
	if(ctx == NULL)
		return NULL;
	
	memset(ctx,0,sizeof(connection_context));
	
	ctx->type = LISTENER;
		
	ctx->fd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
	if(ctx->fd < 0)
		goto fail;
	
	int reuse = 1;
	if(setsockopt(ctx->fd,SOL_SOCKET,SO_REUSEADDR,(void*)&reuse,sizeof(reuse)))
		goto fail;
	
	struct linger lng = {0,0};
	if(setsockopt(ctx->fd,SOL_SOCKET,SO_LINGER,(void*)&lng,sizeof(struct linger)))
		goto fail;
	
	memcpy(&ctx->addr,&addr,sizeof(struct sockaddr_in));
	if(bind(ctx->fd,(struct sockaddr*)&ctx->addr,sizeof(struct sockaddr_in)))
		goto fail;
	
	if(listen(ctx->fd,max_conn))
		goto fail;
	
	ctx->http_srv=h;
	void* argv[1];
	argv[0]=ctx;
	ctx->thread_id = core_thread_create_attached(listener_loop,1,argv);
	return ctx;
	
fail:
	destroy_context(ctx);
	return NULL;
}

connection_context* create_server_context(struct sockaddr_in addr, int fd)
{
	connection_context* ctx = malloc(sizeof(connection_context));
	if(ctx == NULL)
		goto fail;
	
	memset(ctx,0,sizeof(connection_context));
	
	list_init(&ctx->methods);
	
	ctx->type = SERVER;
	ctx->fd = fd;

	struct linger lng = {0,0};
	if(setsockopt(ctx->fd,SOL_SOCKET,SO_LINGER,(void*)&lng,sizeof(struct linger)))
		goto fail;
	
	struct timeval tv;
	tv.tv_sec=5;
	tv.tv_usec=0;
	if(setsockopt(ctx->fd,SOL_SOCKET,SO_RCVTIMEO,(void*)&tv,sizeof(struct timeval)))
		goto fail;
	
	ctx->addr = addr;
	
	size_t optlen = sizeof(size_t);
	if(getsockopt(ctx->fd,SOL_SOCKET,SO_RCVBUF,&ctx->read_buffer_size,(socklen_t*)&optlen))
		goto fail;
	
	stream_init(&ctx->read_stream,UNBOUNDED);
	stream_init(&ctx->write_stream,UNBOUNDED);	
	return ctx;
	
fail:
	destroy_context(ctx);
	return NULL;
}

void destroy_context(connection_context* ctx)
{
	if((ctx->fd >= 0)
#ifdef ENABLE_SSL
	   && (ctx->ssl==NULL)
#endif
	   )
	{
		close(ctx->fd);
	}
	else
	{
#ifdef ENABLE_SSL
		SSL_free(ctx->ssl);
#endif
		close(ctx->fd);
	}
	
	if(ctx->type!=LISTENER)
	{
		stream_cleanup(&ctx->read_stream);
		stream_cleanup(&ctx->write_stream);
	}
	
	if(ctx->thread_id)
		core_thread_destroy(ctx->thread_id);
	
	free(ctx);
}

static void* listener_loop(int argc, void** arg)
{
	connection_context* c = arg[0];
	int fd;
	
	//this context is a listening socket.
	//process connection requests
	//if there are threads to handle them
	while(1)
	{
		fd = 0;
		
		socklen_t len = sizeof(struct sockaddr_in);
		struct sockaddr_in address = {0};
		int fd = accept(c->fd, (struct sockaddr*)&address, (socklen_t*)&len);
		if(fd < 0)
			goto fail;
		
		
		connection_context* ctx = create_server_context(address,fd);
		ctx->srvctx = c;
		
		if(ctx==NULL)
			goto fail;

		ctx->http_srv = c->http_srv;
		
		//the context gets cleaned up by the handle_connection
		//function called in the new thread
		void* argv[1];
		argv[0] = ctx;
		ctx->thread_id = core_thread_create_attached(handle_http_connection,1,argv);
	}

fail:
	//there was an error
	if(c)
		destroy_context(c);
	if(fd > 0)
		close(fd);
	
	return NULL;
}

void context_read(connection_context* ctx, io_result* result, int* bytes_received)
{
#ifdef ENABLE_SSL
	if(ctx->ssl==NULL)
		stream_from_socket(&ctx->read_stream,ctx->fd,result,bytes_received);
	else
		stream_from_ssl_socket(&ctx->read_stream,ctx->ssl,result,bytes_received);
#else
	stream_from_socket(&ctx->read_stream,ctx->fd,result,bytes_received);

#endif
}

void context_wait_write(connection_context* ctx, int timeout_msec, io_result* result, int* bytes_sent)
{
	while(ctx->write_stream.writer_length != ctx->write_stream.reader_length)
	{
		int bytes=0;
#ifdef ENABLE_SSL	
		if(ctx->ssl==NULL)
			stream_to_socket(&ctx->write_stream,ctx->fd,result,&bytes);
		else
			stream_to_ssl_socket(&ctx->write_stream,ctx->ssl,result,&bytes);
#else
		stream_to_socket(&ctx->write_stream,ctx->fd,result,&bytes);
#endif
		*bytes_sent += bytes;
	}
}
